#include "DXUT.h"
#include "SkinnedMesh.h"

//Constructor
SkinnedMesh::SkinnedMesh()
{
	//Set pointer to NULL
	controller = NULL;
	//Set pointer to NULL
	geometry = NULL;
	//Set pointer to NULL
	rootJoint = NULL;		
	//Set vertex type
	m_iVType = VERTEXTYPE::VT_POSNORMUVTANG_SKIN;
	//Stride size
	STRIDE_SIZE = VertexNormUVTangSkinned::STRIDE_SIZE;
	//Initialize Component Vertex Data
	Positions = vector<D3DXVECTOR3>();
	UVs = vector<D3DXVECTOR2>();
	Normals = vector<D3DXVECTOR3>();
	Tangents = vector<D3DXVECTOR3>();
	BiNormals = vector<D3DXVECTOR3>();
	boneIndices = vector<SkinIndex>();
	Weights = vector<SkinWeight>();		
	//Set Name
	this->Name = string("default");
	//Set Root Transform Matrix
	D3DXMatrixIdentity(&(this->RootTransform));
	//Set BindShape Matrix
	D3DXMatrixIdentity(&BindShape);
	//Initialize Joints
	Joints = vector<Joint>();
	//Initialize Time sorted Animation Key list
	Animations = vector<JointAnimationKey>();
	//Initialize Combined Vertex Data, ready for Vertex Buffer
	Vertices = vector<VertexNormUVTangSkinned>();
	//Initialize Index data, ready for Index Buffer
	Indices = vector<unsigned int>();
	//Set accumulatedTime to 0
	accumulatedTime = 0;
}

SkinnedMesh::SkinnedMesh(string Name, D3DXMATRIX RootTransform)
{
	//Set pointer to NULL
	controller = NULL;
	//Set pointer to NULL
	geometry = NULL;
	//Set pointer to NULL
	rootJoint = NULL;		
	//Set vertex type
	m_iVType = VERTEXTYPE::VT_POSNORMUVTANG_SKIN;
	//Stride size
	STRIDE_SIZE = VertexNormUVTangSkinned::STRIDE_SIZE;
	//Initialize Component Vertex Data
	Positions = vector<D3DXVECTOR3>();
	UVs = vector<D3DXVECTOR2>();
	Normals = vector<D3DXVECTOR3>();
	Tangents = vector<D3DXVECTOR3>();
	BiNormals = vector<D3DXVECTOR3>();
	boneIndices = vector<SkinIndex>();
	Weights = vector<SkinWeight>();		
	//Set Name
	this->Name = Name;
	//Set Root Transform Matrix
	this->RootTransform = RootTransform;
	//Set BindShape Matrix
	D3DXMatrixIdentity(&BindShape);
	//Initialize Joints
	Joints = vector<Joint>();
	//Initialize Time sorted Animation Key list
	Animations = vector<JointAnimationKey>();
	//Initialize Combined Vertex Data, ready for Vertex Buffer
	Vertices = vector<VertexNormUVTangSkinned>();
	//Initialize Index data, ready for Index Buffer
	Indices = vector<unsigned int>();
	//Set accumulatedTime to 0
	accumulatedTime = 0;
}



//Placeholder for future improvement
void SkinnedMesh::SetVType(enum VERTEXTYPE VType)
{

}

//OnDeviceReset
HRESULT SkinnedMesh::OnD3D11CreateDevice( ID3D11Device* pd3dDevice, ID3DX11EffectTechnique* pTech,
	const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc, CDirectoryUtil* pDirUtil, void* pUserContext)
{
	HRESULT hr = S_OK;

	//Get vertex buffer size
	if (Vertices.size() == 0)
	{
		printf("No geometry data loaded yet!\n");
#ifdef _DEBUG
		OUTPUTERRORINFO;
#endif
		return E_FAIL;
	}
	//Create vertex layout
	V_RETURN(CD3DUtilityFunction::CreateVertexLayout(pd3dDevice,VertexNormUVTangSkinned::layout,ARRAYSIZE(VertexNormUVTangSkinned::layout),pTech,&m_pVertexLayout));
	//Create VertexBuffer
	V_RETURN(CD3DUtilityFunction::CreateVertexBuffer(pd3dDevice,STRIDE_SIZE, (float*)(Vertices.data()), Vertices.size(), &m_pVB));
	//Create Index Buffer
	V_RETURN(CD3DUtilityFunction::CreateIndexBuffer(pd3dDevice,Indices.size(),(void*)(Indices.data()),&m_pIB));

	return S_OK;
}

//Update with delta time
void SkinnedMesh::Update(float dt)
{
	if(Animations.size() < 1) {
		//Bone Transforms
		for(unsigned int i = 0; i < Joints.size(); i++) boneTransforms[i] = Joints[i].bind_matrix;
		//Calculate Skin
		CalculateSkin();
		return;
	}
	//Add to the accumulatedTime
	accumulatedTime += dt;
	//If the accumulatedTime is greater than the end time set it back to the start
	if(accumulatedTime > Animations.back().Time) accumulatedTime = 0;
	//Bone Transforms
	LinearAnimate();
	//Calculate Skin
	CalculateSkin();
}

//Draw
void SkinnedMesh::Render(ID3D11Device* pd3dDevice, ID3D11DeviceContext* pd3dImmediateContext, ID3DX11Effect* pEffect, 
						ID3DX11EffectTechnique* pRenderTech, enum RENDERMESHTECH iMeshType, const D3DXMATRIX& mWorldCamera, 
						double fTime, float fElapsedTime, void* pUserContext)
{
	if (iMeshType != RENDERMESHTECH::RENDERSKINMESH)
	{
#ifdef _DEBUG
		OUTPUTERRORINFO;
#endif
			return;
	}

	HRESULT hr = S_OK;
	//set world matrix
	D3DXMATRIX mWorldMatrix = GetWorldMatrix() * mWorldCamera;
	hr = (pEffect->GetVariableByName("mWorld")->AsMatrix()->SetMatrix((float*)(&mWorldMatrix)));
	//skin parameters
	hr = (pEffect->GetVariableByName("BindShape")->AsMatrix())->SetMatrix((float*)(&BindShape));
	hr = (pEffect->GetVariableByName("MatrixPallette")->AsMatrix())->SetMatrixArray((float*)(&(Pallette[0])),0,Pallette.size());
	// Set render resources
	pd3dImmediateContext->IASetInputLayout( m_pVertexLayout );
	ID3D11Buffer* pVB[1] = { m_pVB };
	UINT pStride[1] = {STRIDE_SIZE};
	UINT pOffset[1] = { 0 };
	pd3dImmediateContext->IASetVertexBuffers( 0, 1, pVB, pStride, pOffset );
	pd3dImmediateContext->IASetIndexBuffer(m_pIB, DXGI_FORMAT_R32_UINT, 0);
	pd3dImmediateContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	// Render objects here...
	// Apply the technique contained in the effect 
	D3DX11_TECHNIQUE_DESC Desc;
	pRenderTech->GetDesc( &Desc );

	for( UINT iPass = 0; iPass < Desc.Passes; iPass++ )
	{
		pRenderTech->GetPassByIndex( iPass )->Apply( 0, pd3dImmediateContext );
		pd3dImmediateContext->DrawIndexed( Indices.size(), 0, 0 );			
	}
}

//Step Animation
	void SkinnedMesh::StepAnimate()
	{
		//First off set the boneTransforms for each joint to bindpose
		for(unsigned int i = 0; i < Joints.size(); i++) boneTransforms[i] = Joints[i].bind_matrix;
		//Now get the latest animation for each joint
		for(unsigned int i = 0; i < Animations.size(); i++){
			//Check if done
			if(Animations[i].Time > accumulatedTime) break;
			//Set this keyframes joint's boneTransform
			boneTransforms[Animations[i].Bone] = Animations[i].Matrix;
		}
	}

	//Linear Animation
	void SkinnedMesh::LinearAnimate()
	{
		//Keyframe index for current frame
		short* KI1 = new short[Joints.size()];
		//Keyframe index for next frame
		short* KI2 = new short[Joints.size()];
		//Keep track of which joints keyframes have been found, this is to ensure that you will only get one next frame key
		bool* KB = new bool[Joints.size()];
		//Keep a running count of the total joints keyframes found, this is to make an early out and save some time
		unsigned int jointTrack = 1;
		//Keep track of where to search for the next frame
		unsigned int nextSearchStart = 0;
		//Set the boneTransforms for each joint to bindpose, set Keyframe indices to -1 and KB to false
		for(unsigned int i = 0; i < Joints.size(); i++)	{
			//BindPose
			boneTransforms[i] = Joints[i].bind_matrix;
			//Keyframe index for this joint to -1
			KI1[i] = -1; KI2[i] = -1;
			//Set KB for this joint to false
			KB[i] = false;
		}
		//Get the Keyframe index for each joint for this frame
		for(unsigned int i = 0; i < Animations.size(); i++)	{
			//Set nextSearchStart to current i
			nextSearchStart = i;
			//Check if done
			if(Animations[i].Time > accumulatedTime) break;
			//Set this keyframes joint's boneTransform
			KI1[Animations[i].Bone] = (short)(i);
		}
		//Get the Keyframe index for the next frame
		for(unsigned int i = nextSearchStart; i < Animations.size(); i++) {
			//If no next keyframe was found yet for this joint
			if(!KB[Animations[i].Bone])	{
				//Set the next Keyframe index for this joint
				KI2[Animations[i].Bone] = (short)(i);
				//Set the bool for this joint true as we have just found the next Keyframe index
				KB[Animations[i].Bone] = true;
				//Add to the joint complete count
				jointTrack++;
			}

			//If every joint has been processed break!
			if(jointTrack >= Joints.size()) break;
		}
		//Linear interpolation for each joint
		for(unsigned int i = 0; i < Joints.size(); i++)	{
			//Values
			D3DXMATRIX a = (KI1[i] >= 0) ? Animations[KI1[i]].Matrix : boneTransforms[i];
			D3DXMATRIX b = (KI2[i] >= 0) ? Animations[KI2[i]].Matrix : boneTransforms[i];
			//Times
			float T1 = (KI1[i] >= 0) ? Animations[KI1[i]].Time : Animations.front().Time;
			float T2 = (KI2[i] >= 0) ? Animations[KI2[i]].Time : Animations.back().Time;
			//No point interpolating if its the same times, also a division by zero safety check
			if(T1 == T2) {
				boneTransforms[i] = a;
			}
			else {
				//Interpolation modifier
				float s = (accumulatedTime - T1) / (T2 - T1);
				boneTransforms[i] = CD3DUtilityFunction::LerpMatrix(a, b, s);
			}
		}
		//Dereference pointers
		delete KI1;
		delete KI2;
		delete KB;
	}


	//Calculate Skin
	void SkinnedMesh::CalculateSkin()
	{
		//World Transforms
		worldTransforms[0] = boneTransforms[0] * RootTransform;
		for(unsigned int i = 1; i < Joints.size(); i++){
			worldTransforms[i] = boneTransforms[i] * worldTransforms[Joints[i].parentIndex];
		}
		//Skin Transforms
		for(unsigned int i = 0; i < Joints.size(); i++){
			Pallette[i] = Joints[i].inv_bind_matrix * worldTransforms[i];
		}
	}